package com.wallace.downloadserver.filter

import com.wallace.downloadserver.myconst.DefineName
import com.wallace.downloadserver.myconst.ResponseMessage
import com.wallace.downloadserver.redis.getV
import com.wallace.downloadserver.redis.setExpire
import com.wallace.downloadserver.util.myEncrypt.AESCryptUtil
import com.wallace.downloadserver.util.myEncrypt.AESKey
import com.wallace.downloadserver.util.myEncrypt.MessageDigestUtil
import com.wallace.downloadserver.util.myEncrypt.RSAUtil
import com.wallace.downloadserver.util.myToken.TokenContent
import com.wallace.downloadserver.wrapper.DecryptRequestWrapper
import com.wallace.downloadserver.wrapper.EncryptResponseWrapper
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.web.bind.annotation.RequestMethod
import java.util.*
import java.util.concurrent.TimeUnit
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import kotlin.collections.ArrayList

/**
 * @Description:
 * 加解密过滤器
 * @author Wallace
 * @Date 2022/2/11 10:19
 */
class ParseBodyFilter(private var redisAesKeyDB: RedisTemplate<String, Any>? = null) : Filter {

    private val log: Logger = LoggerFactory.getLogger(ParseBodyFilter::class.java)

    private val pathRegex: ArrayList<Regex> = ArrayList()

    override fun init(filterConfig: FilterConfig?) {
        val excludedPaths = filterConfig!!.getInitParameter("excludedPaths")
        excludedPaths.split(";").forEach {
            pathRegex.add(Regex(it))
        }
        log.debug("pathRegex = $pathRegex")
    }

    private fun needFilter(httpServletRequest: HttpServletRequest): Boolean {
        pathRegex.forEach {
            if (httpServletRequest.requestURI.matches(it)) {
                return false
            }
        }
        return true
    }

    override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {
        val httpServletRequest = request as HttpServletRequest
        val httpServletResponse = response as HttpServletResponse
        if (needFilter(httpServletRequest)) {
            var data: ByteArray? = null
            val chainResponse = EncryptResponseWrapper(httpServletResponse)
            when (needDecrypt(httpServletRequest, httpServletResponse)) {
                Result.Need -> {
                    log.info("开始解密")
                    val chainRequest = decrypt(httpServletRequest, httpServletResponse)
                    if (chainRequest != null) {
                        log.info("解密成功")
                        chain!!.doFilter(chainRequest, chainResponse)
                        data = encrypt(chainRequest, chainResponse)
                    } else {
                        log.warn("解密失败")
                        return
                    }
                }
                Result.Skip -> {
                    log.info("[${httpServletRequest.requestURI}]跳过解密")
                    chain!!.doFilter(httpServletRequest, chainResponse)
                    data = encrypt(httpServletRequest, chainResponse)
                }
                Result.Error -> {
                    log.warn("需要解密但又无法解密！！！")
                }
            }
            data?.apply {
                httpServletResponse.outputStream.write(this)
                httpServletResponse.outputStream.flush()
                httpServletResponse.outputStream.close()
            }
        } else {
            chain!!.doFilter(httpServletRequest, httpServletResponse)
        }
    }

    /**
     * 判断body是否empty
     */
    private fun emptyBody(httpServletRequest: HttpServletRequest): Boolean {
        if (httpServletRequest.method == RequestMethod.GET.toString() || httpServletRequest.method == RequestMethod.DELETE.toString()) {
            return true
        } else if (httpServletRequest.contentLength <= 0) {
            return true
        }
        return false
    }

    /**
     *判断是否需要解密
     */
    private fun needDecrypt(httpServletRequest: HttpServletRequest, httpServletResponse: HttpServletResponse): Result {
        val tokenContent = httpServletRequest.getAttribute(DefineName.TOKEN_CONTENT) as? TokenContent
        // 登录请求
        if (tokenContent == null) {
            val aesKey = httpServletRequest.getHeader(DefineName.AES_KEY)
            if (aesKey == null) {
                httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.HEADER_NO_AES_KEY)
                return Result.Error
            } else {
                val iv = httpServletRequest.getHeader(DefineName.AES_IV)
                if (iv == null) {
                    httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.HEADER_NO_AES_IV)
                    return Result.Error
                } else {
                    val privateKey = httpServletRequest.session.getAttribute(DefineName.RSA_PRIVATE_KEY)
                    if (privateKey == null) {
                        httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.SESSION_INVALID)
                        return Result.Error
                    } else {
                        httpServletRequest.setAttribute(DefineName.RSA_PRIVATE_KEY, privateKey)
                    }
                }
            }
        } else {
            // 其他业务请求(必须有token)
            val uid = tokenContent.id.split("-")[1]
            val map = redisAesKeyDB!!.getV(uid) as? Map<*, *>
            if (map == null) {
                httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.NO_KEY)
                return Result.Error
            } else {
                // 刷新时效
                redisAesKeyDB!!.setExpire(uid, TimeUnit.MINUTES.toMillis(30))
                val aesKey = map[DefineName.AES_KEY] as String
                val iv = map[DefineName.AES_IV] as String
                val privateKey = map[DefineName.RSA_PRIVATE_KEY] as String
                httpServletRequest.setAttribute(DefineName.AES_KEY, aesKey)
                httpServletRequest.setAttribute(DefineName.AES_IV, iv)
                httpServletRequest.setAttribute(DefineName.RSA_PRIVATE_KEY, privateKey)
            }
        }
        val signature = httpServletRequest.getHeader(DefineName.RSA_SIGNATURE)
        if (emptyBody(httpServletRequest)) {
            return Result.Skip
        } else {
            if (signature == null) {
                httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.HEADER_NO_SIGNATURE)
                return Result.Error
            }
        }
        return Result.Need
    }

    /**
     * 解密
     */
    private fun decrypt(httpServletRequest: HttpServletRequest, httpServletResponse: HttpServletResponse): DecryptRequestWrapper? {
        val tokenContent = httpServletRequest.getAttribute(DefineName.TOKEN_CONTENT) as? TokenContent
        val privateKey: ByteArray?
        val aesKey: ByteArray?
        val iv: ByteArray?
        val decoder = Base64.getDecoder()
        val privateKeyString = httpServletRequest.getAttribute(DefineName.RSA_PRIVATE_KEY) as String
        // RSA私钥
        privateKey = decoder.decode(privateKeyString.toByteArray())
        // 登录请求
        if (tokenContent == null) {
            val aesKeyString = httpServletRequest.getHeader(DefineName.AES_KEY)
            val ivString = httpServletRequest.getHeader(DefineName.AES_IV)
            val aesKeyByteArray = decoder.decode(aesKeyString.toByteArray())
            val ivByteArray = decoder.decode(ivString.toByteArray())
            iv = RSAUtil.decryptByPrivateKey1(ivByteArray, privateKey)
            aesKey = RSAUtil.decryptByPrivateKey1(aesKeyByteArray, privateKey)
        }
        // 其他业务请求
        else {
            val aesKeyString = httpServletRequest.getAttribute(DefineName.AES_KEY) as String
            val ivString = httpServletRequest.getAttribute(DefineName.AES_IV) as String
            aesKey = decoder.decode(aesKeyString.toByteArray())
            iv = decoder.decode(ivString.toByteArray())
        }
        // 获取请求头中的签名,AES密钥和iv
        val headerSignature = decoder.decode(httpServletRequest.getHeader(DefineName.RSA_SIGNATURE).toByteArray())
        // RSA私钥解密后
        val signature = RSAUtil.decryptByPrivateKey1(headerSignature, privateKey)?.toString(Charsets.UTF_8)
        if (signature == null || iv == null || aesKey == null) {
            // 解密失败，终止后续操作
            httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.DECRYPT_FAIL)
        } else {
            val decryptRequestWrapper = DecryptRequestWrapper(httpServletRequest, AESKey(aesKey, iv))
            // 解密后的明文
            val plainByteArray = decryptRequestWrapper.inputStream.readBytes()
            val plain = plainByteArray.toString(Charsets.UTF_8)
            log.info("server get from client = $plain")
            // 使用MD5对明文做摘要处理
            val plainMD5 = MessageDigestUtil.md5(plain)
            log.info("{server md5, client md5, comparison} = {$plainMD5, $signature, ${plainMD5 == signature}}")
            if (signature != plainMD5) {
                log.info("sever plain : $plain")
                httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, ResponseMessage.Error.TAMPERED_REQUEST)
            } else {
                // 登录请求
                if (tokenContent == null) {
                    val encoder = Base64.getEncoder()
                    decryptRequestWrapper.setAttribute(DefineName.RSA_PRIVATE_KEY, privateKeyString)
                    decryptRequestWrapper.setAttribute(DefineName.AES_KEY, encoder.encode(aesKey).toString(Charsets.UTF_8))
                    decryptRequestWrapper.setAttribute(DefineName.AES_IV, encoder.encode(iv).toString(Charsets.UTF_8))
                }
                return decryptRequestWrapper
            }
        }
        return null
    }

    /**
     * 加密返回的数据
     */
    private fun encrypt(httpServletRequest: HttpServletRequest, responseWrapper: EncryptResponseWrapper): ByteArray? {
        // 明文
        val plainByteArray = responseWrapper.getContent()
        log.info("server send to client(plain) = ${plainByteArray.toString(Charsets.UTF_8)}")
        // 处理response加密
        if (plainByteArray.isNotEmpty()) {
            val aesKeyString = httpServletRequest.getAttribute(DefineName.AES_KEY) as? String
            val ivString = httpServletRequest.getAttribute(DefineName.AES_IV) as? String
            if (aesKeyString != null && ivString != null) {
                val decoder = Base64.getDecoder()
                val aesKey = decoder.decode(aesKeyString.toByteArray())
                val iv = decoder.decode(ivString.toByteArray())
                val cipherByteArray = AESCryptUtil.encryptAndEncode(plainByteArray, AESKey(aesKey, iv).getSecretKeySpec(), iv)
                log.info("server send to client(cipher) = ${cipherByteArray?.toString(Charsets.UTF_8)}")
                return cipherByteArray
            }
        }
        return null
    }

    enum class Result {
        Need,
        Skip,
        Error
    }
}